/*
    TreeGrid.js
    Copyright (C) 2011, Donald W. Griffin
    All Rights Reserved
*/
Ext.namespace("Wdg");

(function (TG) {
    TG.initComponent = TG.initComponent.createInterceptor(function () {
        this.addEvents(
            "afterrendernode"
        );
        return true;
    });

})(Ext.ux.tree.TreeGrid.prototype);

(function (UI) {
    // args: (node, attrs, targetNode, bulkRender)
    UI.renderElements = UI.renderElements.createSequence(function (node) {
        // The "cls" property of the 0'th column is not rendered on the TD element
        // like it is for all other columns...
        var tree = node.ownerTree, cls = tree.columns[0].cls;
        if (cls)
            Ext.fly(this.anchor.parentNode).addClass(cls);

        tree.fireEvent("afterrendernode", tree, node);
    });

})(Ext.ux.tree.TreeGridNodeUI.prototype);

(function () {
    function extend (C, xtype, reg) {
        var T = Ext.extendX(C, function (base) {
            return {
                constructor: function (cfg) {
                    base.constructor.call(this, cfg);

                    var ed = this.editor;
                    delete this.editor;
                    if (!ed && this.editable)
                        ed = {};
                    if (ed && typeof(this.editable) !== "boolean")
                        this.editable = true;

                    this.setEditor(ed);
                },

                destroy: function () {
                    this.setEditor(null);
                },

                getCellEditor: function (node) {
                    var ed = this.getEditor(node);
                    if (ed && !ed.startEdit) {
                        ed = ed.gridEditor || (ed.gridEditor = new Ext.grid.GridEditor(ed));
                    }
                    return ed;
                },

                getEditor: function (node) {
                    return this.editable ? this.editor : null;
                },

                setEditor: function (ed) {
                    var old = this.editor;
                    if (old) {
                        if (old.gridEditor) {
                            old.gridEditor.destroy();
                            delete old.gridEditor;
                        } else {
                            old.destroy();
                        }
                    }

                    this.editor = ed ? (ed.isXType ? ed : Ext.create(ed, xtype)) : null;
                }
            };
        });

        Ext.reg(reg, T);
        return T;
    }

    Wdg.TreeGridColumn = extend(Ext.tree.Column, 'textfield', 'tgcolumn');
    Wdg.TreeGridNumberColumn = extend(Ext.tree.NumberColumn, 'numberfield', 'tgnumbercolumn');
    Wdg.TreeGridDateColumn = extend(Ext.tree.DateColumn, 'datefield', 'tgdatecolumn');
    Wdg.TreeGridBooleanColumn = extend(Ext.tree.BooleanColumn, 'checkbox', 'tgbooleancolumn');
})();

Wdg.TreeGridPlugin = Ext.extendX(Object, function (base) {

    function colRenderer (v) {
        return v;
    }

    function colText (col) {
        return function () {
            return col.text;
        };
    }

    function colTpl (tpl) {
        return function (v, node, rec) {
            return tpl.apply(rec.data);
        };
    }

    function doRender (col, colIndex, rec, node) {
        var v = rec.data[col.dataIndex];
        return col.renderer.call(col.scope || col, v, node, rec, colIndex);
        // grid: renderer(v, meta, rec, rowIndex, colIndex, store)
    }

    // Added to TreeGrid (this = TreeGrid)
    function getTreeGridAddons (tg, plugin) {
        return {
            _addonsPresent: true,
            _recNodeMap: {},
            _stores: [],

            initColumns: tg.initColumns.createSequence(function () {
                Ext.each(this.columns, function (col, colIndex) {
                    if (!col.renderer) {
                        col.renderer = col.tpl ? colTpl(col.tpl)
                                        : (col.dataIndex ? colRenderer : colText(col));
                        col.tpl = null;
                    }

                    if (!col.tpl) {
                        col.tpl = new Ext.XTemplate("{[this.render(values)]}", {
                            render: function (a) {
                                return doRender(col, colIndex, a[plugin.recordAttr], a.$node);
                            }
                        });
                    }
                },
                this);
            }),

            initEvents: tg.initEvents.createSequence(function () {
                if (this.store) {
                    this.addStore(this.store);
                }
            }),

            registerNode: tg.registerNode.createSequence(function (node) {
                var a = node.attributes, rec = a[plugin.recordAttr], sa = plugin.storeAttr,
                    ds = a[sa];
                if (ds)
                    this.addStore(ds);

                if (!rec) {
                    var recId = plugin.recordIdAttr;
                    recId = recId && a[recId];
                    if (recId !== null && typeof(recId) !== "undefined") {
                        for (var pn = node; !!(pn = pn.parentNode); ) {
                            ds = pn.attributes[sa];
                            if (ds) {
                                rec = ds.getById(recId);
                                break;
                            }
                        }
                    }
                    if (!rec)
                        return;
                }

                var nid = node.id, rid = rec.id, d = rec.data, map = this._recNodeMap;
                var en = map[rid];

                if (!en) {
                    map[rid] = nid;
                } else if (Ext.isArray(en)) {
                    en.push(nid);
                } else {
                    map[rid] = [en, nid];
                }

                rec.fields.each(function (f) {
                    if (f.name != "id")
                        a[f.name] = d[f.name];
                })

                a.$node = node;
            }),

            unregisterNode: tg.unregisterNode.createSequence(function (node) {
                var a = node.attributes, rec = a[plugin.recordAttr], ds = a[plugin.storeAttr];
                if (ds)
                    this.removeStore(ds);
                if (!rec)
                    return;

                var nid = node.id, rid = rec.id, map = this._recNodeMap;
                var en = map[rid];

                if (!Ext.isArray(en)) {
                    delete map[rid];
                } else {
                    en.remove(nid);
                    if (en.length < 2) {
                        map[rid] = en[0];
                    }
                }
            }),

            // new methods

            addStore: function (store) {
                this._stores.push(store);
                this.mon(store, "update", this._onUpdateStore, this);
            },

            getColumnsForNode: function (node) {
                return this.getVisibleColumns();
            },

            removeStore: function (store) {
                for (var i = this._stores.length; i-- > 0; ) {
                    if (store === this._stores[i]) {
                        this._stores.splice(i, 1);
                        this.mun(store, "update", this._onUpdateStore, this);
                        break;
                    }
                }
            },

            _onUpdateStore: function (store, rec) {
                var rid = rec.id, map = this._recNodeMap, en = map[rid];
                if (Ext.isArray(en)) {
                    Ext.each(en, this._updateNodeById, this);
                } else if (rid in map) {
                    this._updateNodeById(en);
                }
            },

            _updateNodeById: function (nodeId) {
                var node = this.getNodeById(nodeId), cols = this.getColumnsForNode(node);
                var i = 0, rec = node[plugin.recordAttr];

                for (var c = node.ui.elNode.firstChild; c; c = c.nextSibling, ++i) {
                    var s = doRender(cols[i], i, rec, node);
                    c.firstChild.innerHTML = s;
                }
            }
        };
    }

    return {
        recordAttr: "rec",
        recordIdAttr: "recId",
        storeAttr: "store",

        constructor: function (cfg) {
            Ext.apply(this, cfg);
        },

        init: function (tg) {
            this.treeGrid = tg;

            if (!tg._addonsPresent) {
                Ext.apply(tg, getTreeGridAddons(tg, this));
            }

            var fn = this.afterRenderNode;
            if (fn)
                tg.on("afterrendernode", fn, this);

            tg.on({
                afterrender: this.onAfterRender,
                scope: this
            });
        },

        initEvents: Ext.emptyFn,

        onAfterRender: function () {
            this.initEvents();
        }
    };
});  // TreeGridPlugin

Wdg.TreeGridColumnModel = Ext.extendX(Wdg.TreeGridPlugin, function (base) {
    var regex = /^tg/, nbsp = "&#160;";

    function bindTpl (tpl) {
        if (Ext.isString(tpl))
            tpl = new Ext.XTemplate(tpl);

        return function (v, node, rec) {
            return tpl.apply(rec.data);
        };
    }

    function empty (me, col) {
        var cr = col.renderer;
        return function (v, node, rec) {
            var nc = me.getNodeColumns(node);
            if (nc === me.columns)
                return nbsp;
            return cr.call(col, node);
        };
    }

    function renderCol (me, c, gc) {
        var cr = gc.renderer;
        return function (v, node, rec) {
            var nc = me.getNodeColumns(node);
            if (nc === me.columns)
                return c.renderer.apply(c, arguments);
            return cr.call(gc, node);
        };
    }

    function getNodeColumnByIndex (node, index) {
            var nc = this.getNodeColumns(node);
            if (nc === tg.columns) {
                var c = nc[index];
                if (c)
                    return c;
                if (typeof(c) === "undefined")
                    return null;
                // else, pass thru column
            }

            return fn.call(tg.treeGrid, node, index);
    }

    function getNodeColumns (node) {
        for (var cp, n = node; n; ) {
            cp = n.attributes[this.columnsProp];
            if (cp && cp.mode == "self")
                return cp.columns;

            var p = n.previousSibling;
            if (p) {
                n = p;
            } else if (!!(p = n.parentNode)) {
                cp = p.attributes[this.columnsProp];
                if (cp)
                    return cp.columns;
                n = p;
            }
        }

        var tree = node.getOwnerTree();
        return tree.getVisibleColumns();
    }

    return {
        /**
        @prop columnsProp The name of the attribute holding a Wdg.TreeGridColumnModel
        */
        columnsProp: "colModel",
        showHeaders: true,
        mode: "child",

        cls: "wdg-treegrid-columns",
        cellCls: "x-grid3-hd-inner",
        headerCls: "x-grid3-header x-grid3-hd x-grid3-cell x-treegrid-hd",
        headerRowCls: "x-grid3-hd-row",

        init: function (tg) {
            base.init.call(this, tg);

            if (!this.cellTpl)
                this.cellTpl = new Ext.XTemplate('<div class="',this.cellCls,'">{header}</div>');
            else if (!this.cellTpl.applyTemplate)
                this.cellTpl = new Ext.XTemplate(this.cellTpl);

            var cols = this.columns;
            this.columns = [];
            Ext.each(cols, this.initColumn, this);

            var c, me = this, gc = me.treeGrid.columns;
            for (var i = this.columns.length, n = gc.length; i < n; ++i) {
                c = gc[i];
                c.renderer = empty(this, c);
            }

            if (!tg.getNodeColumnByIndex)
                tg.getNodeColumnByIndex = getNodeColumnByIndex;
        },

        initColumn: function (c) {
            var gc = this.treeGrid.columns[this.columns.length];
            if (typeof(c) === "undefined") {
                gc.renderer = empty(this, gc);
            } else if (c) {
                if (!c.isColumn) {
                    c.xtype = c.xtype ?
                            (regex.test(c.xtype) ? c.xtype : 'tg'+c.xtype) : 'tgcolumn';
                    c = Ext.create(c);
                }
                if (!c.renderer)
                    c.renderer = bindTpl(c.tpl);

                c.init(this.treeGrid);
                gc.renderer = renderCol(this, c, gc);
            }

            c.colModel = this;
            this.columns.push(c);
        },

        afterRenderNode: function (tg, node) {
            if (!this.showHeaders)
                return;

            var v;

            if (this.mode == "self") {
                v = node.attributes[this.columnsProp];
            } else { // if ("child")
                var pn = node.parentNode;
                v = pn && (pn.firstChild.id === node.id) && pn.attributes[this.columnsProp];
            }

            if (v === this)
                this.render(node);
        },

        getNodeColumns: function (node) {
            var c = node.attributes[this.columnsProp];
            return c ? c.columns : this.treeGrid.columns;
        },

        render: function (node) {
            var row, parent = node.ui.elNode.parentNode, tbody = parent.cloneNode(false);

            Ext.each(this.columns, function (c) {
                if (row)
                    row.push({
                        tag: "td", cls: this.headerCls,
                        html: c ? this.cellTpl.apply(c) : nbsp
                    });
                else
                    row = [{ tag: "td", html: nbsp }];
            }, this);

            Ext.DomHelper.createDom({
                tag: "tr", cls: this.headerRowCls, children: row
            }, tbody);

            parent.parentNode.insertBefore(tbody, parent);
            Ext.fly(node.ui.ctNode).addClass(this.cls);
            Ext.fly(parent).addClass(this.cls);
        }
    };
});

Wdg.TreeGridCellEvents = Ext.extendX(Wdg.TreeGridPlugin, function (base) {
    var columnClsPrefix = "wdg-treegrid-col-";
    var columnClsRegex = /wdg\-treegrid\-col\-([^\s]+)/;

    return {

        init: function (tg) {
            base.init.call(this, tg);

            tg.addEvents(
                "cellclick",
                "celldblclick"
            );

            var i = 0, ids = {};
            Ext.each(tg.columns, function (col) {
                if (!col.id) {
                    if (col.dataIndex && !(col.dataIndex in ids))
                        col.id = col.dataIndex;
                    else
                        col.id = "col-gen" + i;
                }
                ids[col.id] = { id: col.id, index: i++, col: col };
                var s = columnClsPrefix + col.id;
                col.cls = col.cls ? (col.cls + ' ' + s) : s;
            }, this);

            this.columnsById = ids;
        },

        initEvents: function () {
            base.initEvents.call(this);

            var tg = this.treeGrid;
            tg.getTreeEl().on({
                click: this.onClick,
                dblclick: this.onDblClick,
                scope: this
            });
        },

        getCellInfoByColumnId: function (node, colId) {
            var cls = columnClsPrefix + colId;
            for (var c = node.ui.elNode.firstChild; c; c = c.nextSibling) {
                if (Ext.fly(c).hasClass(cls))
                    return this.getCellElInfo(node, c);
            }
            return null;
        },

        getCellElInfo: function (node, td) {
            var m = td && td.className.match(columnClsRegex);
            if (m && m[1]) {
                var ci = Ext.apply({}, this.columnsById[m[1]]), tg = this.treeGrid;
                ci.node = node;
                if (tg.getNodeColumnByIndex) {
                    var col = tg.getNodeColumnByIndex(ci.node, ci.index);
                    if (!col)
                        return null;
                    ci.baseCol = ci.col;
                    ci.col = col;
                    ci.el = ci.index ? td : node.ui.anchor;
                }
                return ci;
            }
            return null;
        },

        getClickInfo: function (e) {
            var td = e.getTarget(".x-treegrid-col");
            if (td) {
                var t = e.getTarget();
                if (t !== td) {
                    if (!Ext.fly(t).hasClass("x-treegrid-text")) {
                        t = e.getTarget("a.x-tree-node-anchor");
                        if (!t)
                            td = null;
                    }
                }

                var ci = this.getCellElInfo(this.treeGrid.eventModel.getNode(e), td);
                return ci;
            }

            return null;
        },

        onClick: function (e) {
            this.processEvent("cellclick", e);
        },

        onDblClick: function (e) {
            this.processEvent("celldblclick", e);
        },

        processEvent: function (name, e) {
            var ci = this.getClickInfo(e);
            if (ci) {
                ci.event = e;
                this.treeGrid.fireEvent(name, this.treeGrid, ci);
            }
        }
    };
});  // TreeGridCellEvents

Wdg.TreeGridEditor = Ext.extendX(Wdg.TreeGridCellEvents, function (base) {

    return {
        autoClickEdit: true,
        autoEncode: false,
        forceValidation: false,
        singleClickEdit: true,

        initEvents: function () {
            base.initEvents.call(this);

            var tg = this.treeGrid;

            tg.addEvents(
                "beforeedit",
                "afteredit",
                "validateedit"
            );

            if (this.singleClickEdit) {
                tg.on("cellclick", this.onCellEditClick, this);
            } else {
                /*if (this.autoClickEdit) {
                    tg.on("mousedown", this.onAutoCellEditClick, this);
                }*/
                tg.on("celldblclick", this.onCellEditClick, this);
            }

            Ext.apply(tg, {
                getCellEditEl: this.getCellEditEl.bind(this, tg),
                getEditorParent: this.getEditorParent.bind(this, tg),
                onEditComplete: this.onEditComplete.bind(this, tg),
                preEditValue: this.preEditValue.bind(this, tg),
                postEditValue: this.postEditValue.bind(this, tg),
                startEditing: this.startEditing.bind(this, tg),
                stopEditing: this.stopEditing.bind(this, tg)
            });
        },

        onAutoCellEditClick: function (treeGrid, info) {
            console.info("autoCellEditClick");
        },

        onCellEditClick: function (treeGrid, info) {
            if (info.col.editable) {
                treeGrid.startEditing(info.node, info.index);
            }
        },

        //-------------------------------------------
        // Added to TreeGrid (tg = TreeGrid)

        getCellEditEl: function (tg, node, colIndex) {
            return node.ui.elNode.childNodes[colIndex];
        },

        getEditorParent: function (tg) {
            var el = tg.getTreeEl();
            return el;
        },

        onEditComplete: function (tg, ed, value, startValue) {
            tg.editing = false;
            tg.lastActiveEditor = tg.activeEditor;
            tg.activeEditor = null;

            var ctx = ed.context, a = ctx.node.attributes, field = ctx.field;
            value = tg.postEditValue(value, startValue, ctx.node, field);
            var s = String(value), ss = String(startValue);
            if (this.forceValidation === true || s !== ss) {
                var e = Ext.applyIf({
                    originalValue: startValue,
                    value: value,
                    cancel: false
                }, ctx);

                if (tg.fireEvent("validateedit", e) !== false && !e.cancel && s !== ss) {
                    a[field] = e.value;
                    var info = this.getCellInfoByColumnId(ctx.node, ctx.colId);
                    info.el.firstChild.innerHTML = ctx.column.renderer(ctx.node);
                    delete e.cancel;
                    tg.fireEvent("afteredit", e);
                }
            }
            //this.view.focusCell(ed.row, ed.col);
        },

        preEditValue: function (tg, node, field) {
            var v = node.attributes[field];
            return this.autoEncode && Ext.isString(v) ? Ext.util.Format.htmlDecode(v) : v;
        },

        postEditValue: function (tg, v, originalValue, node, field) {
            return this.autoEncode && Ext.isString(v) ? Ext.util.Format.htmlEncode(v) : v;
        },

        startEditing: function (tg, node, colIndex) {
            var col = tg.getNodeColumnByIndex ? tg.getNodeColumnByIndex(node, colIndex)
                        : tg.columns[colIndex];
            var e = {
                    treeGrid: tg,
                    node: node,
                    column: col,
                    colId: tg.columns[colIndex].id,
                    index: colIndex,
                    field: col.dataIndex,
                    value: node.attributes[col.dataIndex],
                    cancel: false
                };

            if (tg.fireEvent("beforeedit", e) !== false && !e.cancel) {
                tg.editing = true;
                var ed = col.getCellEditor(node);
                if (!ed) {
                    return;
                }

                if(!ed.rendered){
                    ed.parentEl = tg.getEditorParent(ed);
                    ed.on({
                        scope: tg,
                        render: {
                            fn: function(c){
                                c.field.focus(false, true);
                            },
                            single: true
                        },
                        //specialkey: function(field, e){
                        //    this.getSelectionModel().onEditorKey(field, e);
                        //},
                        complete: tg.onEditComplete,
                        canceledit: tg.stopEditing.bind(tg, true)
                    });
                }
                ed.context = tg.lastEdit = e;

                tg.activeEditor = ed;
                // Set the selectSameEditor flag if we are reusing the same editor again
                // and need to prevent the editor from firing onBlur on itself.
                ed.selectSameEditor = (tg.activeEditor == tg.lastActiveEditor);
                var v = tg.preEditValue(node, col.dataIndex);
                v = Ext.isDefined(v) ? v : "";
                ed.startEdit(tg.getCellEditEl(node, colIndex), v);

                // Clear the selectSameEditor flag
                (function(){
                    delete ed.selectSameEditor;
                }).defer(50);
            }
        },

        stopEditing : function (tg, cancel) {
            if (tg.editing) {
                // Store the lastActiveEditor to check if it is changing
                var ae = tg.lastActiveEditor = tg.activeEditor;
                if (ae) {
                    ae[cancel ? 'cancelEdit' : 'completeEdit']();
                    //this.view.focusCell(ae.row, ae.col);
                }
                tg.activeEditor = null;
            }
            tg.editing = false;
        }
    };
});  // TreeGridCellEditor

//-----------------------------------------------------------------------------

Wdg.TreeGridRowActions = Ext.extend(Wdg.ActionStripPlugin, {
    actionSelectsItem: true,

    // Column stuff
    dataIndex: "",
    header: "",
    isColumn: true,
    fixed: true,
    menuDisabled: true,
    sortable: false,

    constructor: function(config) {
        Wdg.TreeGridRowActions.superclass.constructor.call(this, config);

        if (!this.width)
            this.width = this.actions.width;

        // used to render into the treeGrid cell:
        var self = this;
        this.tpl = new Ext.XTemplate("{[this.render(values)]}", {
            render: function (values) {
                return self.actions.render({
                    baseId: values.id + '_',
                    values: values
                });
            }
        });
    },

    // Columns in a TreeGrid are init-ed just like a plugin...
    init: function (treeGrid) {
        Wdg.TreeGridRowActions.superclass.init.call(this, treeGrid);

        var em = treeGrid.eventModel;
        em.beforeEvent = em.beforeEvent.createInterceptor(this.onEvent, this);
    },

    onEvent: function (e) {
        if (e.type === "click") {
            var ed = this.actions.matchEvent(e);
            if (ed) {
                var node = this.component.eventModel.getNode(e);
                Ext.apply(ed, {
                    node: node,
                    values: node.attributes
                });

                this.actions.dispatch(ed);
                return ed.selectItem; // maybe pass on to tree.eventModel.beforeEvent
            }
        }
        return true; // pass on
    }
}); // Wdg.TreeGridRowActions
